package cn.zyl.spring.boot.branch.tools;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.stream.Stream;

/** ArrayTool工具类说明: 数组工具类
 * @author z.y.l
 * @date 2020-09-14
 * @version v1.0
 */
public class ArrayTool {
    private static final int N_0 = 0;
    private static final int N_1 = 1;
    private ArrayTool(){throw new IllegalAccessError("ArrayTool");}
    /**
     * 对 数组 进行拷贝，不改变参数内容
     * @param ts 原数组
     * @param len 原数组长度
     * @return 复制的数组
     */
    @SuppressWarnings("unchecked")
    public static <T> T[] copyArray(T[] ts,int len){
        if(len <= N_0 || null == ts || ts.length <= N_0){ return (T[]) new Object[N_0]; }
        return Arrays.copyOf(ts, len);
    }
    /**
     * 对 数组 进行拷贝，不改变参数内容
     * @param ts 原数组
     * @return 复制的数组
     */
    public static <T> T[] copyArray(T[] ts){
        return copyArray(ts,ts.length);
    }
    /**
     * 判断数组是否有序
     * @param ts 数组
     * @return ts是否有序
     */
    public static <T extends Comparable<T>> boolean isSorted(T[] ts) { return isSorted(ts, 0, ts.length - 1); }
    public static <T extends Comparable<T>> boolean isSorted(List<T> list) { return isSorted(list, 0, list.size() - 1); }
    /**
     * 判断数组ts[start...end]区间是否有序
     */
    public static <T extends Comparable<T>> boolean isSorted(T[] ts, int start, int end) {
        start = Math.max(start, 0);
        end = Math.min(end, ts.length);
        for (int i = start + 1; i <= end; i++) {
            if (lessTo(ts[i], ts[i - 1])) { return false; }
        }
        return true;
    }
    /**
     * 判断集合list[start...end]区间是否有序
     */
    public static <T extends Comparable<T>> boolean isSorted(List<T> list,int start,int end){
        start = Math.max(start, 0);
        end = Math.min(end, list.size());
        for (int i = start + 1; i <=end ; i++) {
            if(lessTo(list.get(i), list.get(i - 1))){return false;}
        }
        return true;
    }
    /**
     * 比较t1和t2, 返回t1是否比t2小
     *
     * @param t1 值1
     * @param t2 值2
     * @return t1 < t2 true;t1 >= t2 false
     */
    public static <T extends Comparable<T>> boolean lessTo(T t1, T t2) { return t1.compareTo(t2) < 0; }
    /**
     * 控制台打印数组
     * @param arr 数组
     */
    private static <T> void printArray(T[] arr){
        System.out.print("[");
        Arrays.stream(arr).forEach(item -> System.out.print(item+","));
        System.out.println("]");
    }

    /**
     * 乱序数组（洗牌算法实现）
     * @param arr 原数组
     * @param <T> 数组泛型
     * @return 乱序数组
     */
    public static <T> T[] randomArrays(T[] arr){
        return randomArrays(arr, arr.length);
    }

    /**
     * 根据已知数组，生成随机数组(单层循环处理)
     * @param arr 已知数组
     * @param size 随机数组长度。小于等于 arr长度，随机数组根据arr不重复下标生成；大于arr长度，不做下标排重处理
     * @param <T> 数组泛型
     * @return 随机数组
     */
    @SuppressWarnings("unchecked")
    public static <T> T[] randomArrays(T[] arr,int size){
        //个数是否小于0
        size = Math.max(N_0, size);
        //初始化数组，可能长度为0
        Object[] os = new Object[size];
        if(size <= N_0 || null == arr || arr.length <= N_0){return (T[])os;}
        //获取原数组长度，最大下标,随机下标
        int len = arr.length,randomIndex;
        Random random = new Random();
        //拷贝数组
        T[] ts = copyArray(arr);
        if(size <= len){
            //洗牌算法
            /*假设数组arr长度为n。
            用Random随机函数nextInt(n)生成[0, n-1]之间的一个随机数，将arr[n-1]和arr[rand(n)]交换，这样就把arr[n-1]位置上的数确定了；
            再将arr[n-2]和arr[nextInt(n-1)]交换，确定array[n-2]位置上的数;
            。。。
            最后将arr[0]和arr[nextInt(1)]交换（实际就是自己和自己交换），把arr[0]确定了
            这样生成的arr[0..n-1]的数组是完全随机的乱序，且时间复杂度为O(n)，空间复杂度为O(1)*/
            for (int i = 0,max = len ; i < size; i ++) {
                //最后一个直接处理结束
                if(max <= 0){ os[i] = ts[0];break; }
                randomIndex = random.nextInt(max --);
                os[i] = ts[randomIndex];
                ts[randomIndex] = ts[max];
            }
            return (T[])os;
        }
        //stream 循环操作数组
        Stream.iterate(N_0, index -> index+1 ).limit(size).forEach(index -> os[index] = ts[random.nextInt(len)]);
        return (T[])os;
    }
    /**
     * 通过 临时变量 进行值交换
     * @param arr 待处理的数组
     * @param index1 待交换数据的下标1
     * @param index2 待交换数据的下标2
     * @param <T> 数组泛型
     */
    public static <T> void swapVal(T[] arr,int index1,int index2){
        T temp = arr[index1];
        arr[index1] = arr[index2];
        arr[index2] = temp;
    }
    /**
     * 在数组中交换索引i, j对应元素
     * @param ts 数组
     * @param i 索引i
     * @param j 索引j
     */
    public static <T extends Comparable<T>> void swap(T[] ts, int i, int j) { T tmp = ts[i];ts[i] = ts[j];ts[j] = tmp;}
    //-----------------------
    /**
     * 通过 位运算 进行数字交换
     * @param arr 待处理的数组
     * @param index1 待交换数据的下标1
     * @param index2 待交换数据的下标2
     */
    public static void swapValBit(Integer[] arr,int index1,int index2){
        if(index1 == index2 || arr[index1].equals(arr[index2])) { return; }
        arr[index1] = arr[index1] ^ arr[index2];
        arr[index2] = arr[index2] ^ arr[index1];
        arr[index1] = arr[index1] ^ arr[index2];
    }
    /**
     * 通过 加减运算 进行数字交换
     * @param arr 待处理的数组
     * @param index1 待交换数据的下标1
     * @param index2 待交换数据的下标2
     */
    public static void swapValArithmetic(Integer[] arr,int index1,int index2){
        if(index1 == index2 || arr[index1].equals(arr[index2])) { return; }
        arr[index1] = arr[index1] + arr[index2];
        arr[index2] = arr[index1] - arr[index2];
        arr[index1] = arr[index1] - arr[index2];
    }
    /**
     * 随机数组生成
     * <p>可能存在重复数据，最小值0</p>
     * <p>使用Stream流的方式生成</p>
     * @param len 数组长度
     * @param max 最大值(正数)
     * @return 随机数组
     */
    public static Integer[] randomArrayByStream(int len,int max){
        Random random = new Random();
        if(len <= N_0 || max <= N_0){return new Integer[N_0];}
        Integer[] arr = new Integer[len];
        //jdk8 使用lambda和流 遍历 数组 同时获取 数组 下标
        Stream.iterate(N_0, index-> index+1).limit(len).forEach(index -> arr[index] = random.nextInt(max));
        return arr;
    }
    /**
     * 随机指定范围内N个不重复的数
     * <p>min和max差距太小，会造成随机数重复率过高，性能会变差</p>
     * <p>利用HashSet的特征，只能存放不同的值，单层for循环</p>
     * @param min 指定范围最小值
     * @param max 指定范围最大值
     * @param size 随机数个数,数组长度
     */
    public static Integer[] randomArrayBySet(int min, int max, int size) {
        if (size > (max - min + N_1) || max < min) { return null; }
        HashSet<Integer> set = new HashSet<>();
        Random random = new Random();

        for (int i = N_0,len = size; i < len; i++) {
            int num = random.nextInt(max) + min;
            set.add(num);
            //如果存入的数小于指定生成的个数，长度开始累加，取代递归模式
            if(i >= len-N_1 && set.size() < size){
                len+=N_1;
            }else if(set.size() >= size){
                break;
            }
        }
        return set.toArray(new Integer[size]);
    }
    /**
     * 随机指定范围内N个不重复的数(洗牌算法)
     * <p>min和max差距过大，会造成待选数组过大，空间浪费大</p>
     * <p>在初始化的无重复待选数组中随机产生一个数放入结果中</p>
     * <p>将待选数组被随机到的数，用待选数组(len-1)下标对应的数替换</p>
     * <p>然后从len-2里随机产生下一个随机数，如此类推</p>
     * @param max  指定范围最大值
     * @param min  指定范围最小值
     * @param size  随机数个数
     * @return int[] 随机数结果集
     */
    public static Integer[] randomArrayByIndex(int min,int max,int size){
        //待选数组长度，随机下标
        int len = max-min+1,index;
        if(max < min || size > len){ return null; }
        //添加简单随机种子
        Random random = new Random();
        //初始化给定范围的待选数组，结果数组
        Integer[] source = new Integer[len],result = new Integer[size];
        for (int i = min; i <= max; i++){
            source[i-min] = i;
        }
        for (int i = 0; i < size; i++) {
            //待选数组0到(len-2)随机一个下标
            index = Math.abs(random.nextInt() % len--);
            //将随机到的数放入结果集
            result[i] = source[index];
            //将待选数组中被随机到的数，用待选数组(len-1)下标对应的数替换
            source[index] = source[len];
        }
        return result;
    }
    public static void main(String[] args) {
        String[] arr = {"a","b","c","d","e","f","1"};
        System.out.println(Arrays.toString(arr));
        System.out.println(Arrays.toString(randomArrays(arr)));
        System.out.println(Arrays.toString(randomArrays(arr,arr.length*2)));
        System.out.println(Arrays.toString(randomArrays(arr,arr.length-2)));
        int len =10,max=150,min=5;
        System.out.println("随机数组（含重复数）-使用Stream流的方式生成");
        printArray(randomArrayByStream(len ,max));
        System.out.println("随机数组（排重-适合min和max的差值远远大于len）-利用HashSet的特征，只能存放不同的值，单层for循环");
        printArray(randomArrayBySet(min,max,len));
        System.out.println("随机数组（排重-适合min和max的差值大于等于len）-利用待选数组选取随机下标，两个for循环");
        printArray(randomArrayByIndex(min,max,len));
    }
}